package com.boot.start.config.log;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Map;

/**
 * @author 墨龙吟
 * @version 1.0.0
 * @ClassName SystemAspect.java
 * @Email 2354713722@qq.com
 * @Description 系统日志切面AOP
 * @createTime 2020年05月18日 - 22:29
 */
@Slf4j
@Aspect
@Component
public class SystemAspect {

    private final static String ERROR_INFO = " [ 异常信息: {} ] ";

    private final static String UNKNOWN = "unknown";

    private final static String DEFAULT_ADDR = "0:0:0:0:0:0:0:1";

    private final static String DEFAULT_IP = "127.0.0.1";

    /** 存储访问前的时间 */
    private final ThreadLocal<Long> startTime = new ThreadLocal<>();

    /** 定义controller面切点 */
    @Pointcut(value = "@annotation(com.boot.start.config.log.SystemLog)")
    public void controllerAspect() {
    };

    /**
     * 获取IP真实地址
     *
     * @param request
     * @return
     */
    private String getAddrIp(HttpServletRequest request) {
        if (request != null) {
            // 如果通过了多级反向代理的话，X-Forwarded-For的值并不止一个，而是一串IP值
            String ip = request.getHeader("x-forwarded-for");
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy - Client - IP");
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
            }
            if (!StringUtils.isEmpty(ip) && !UNKNOWN.equalsIgnoreCase(ip)) {
                ip = ip.split(",")[0].trim();
            }
            if (DEFAULT_ADDR.equalsIgnoreCase(ip)) {
                ip = DEFAULT_IP;
            }
            return ip;
        }
        return "获取IP失败";
    }



    /**
     * 获取方法请求参数
     *
     * @param joinPoint
     */
    private void getMethodParam(ProceedingJoinPoint joinPoint) {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        boolean inputParam = methodSignature.getMethod().getAnnotation(SystemLog.class).inputParam();
        if(inputParam){
            Object[] args = joinPoint.getArgs();
            if (args != null && args.length > 0) {
                for (Object arg : args) {
                    if (arg instanceof HttpServletRequest) {
                        HttpServletRequest request = (HttpServletRequest) arg;
                        String contentType = request.getContentType();
                        if (contentType != null && contentType.contains("application/json")) {
                            continue;
                        } else {
                            Map<String, String[]> parameterMap = request.getParameterMap();
                            log.info(" [ 请求参数: {} ]", parameterMap);
                            break;
                        }
                    }
                }
            }
        }
    }



    /**
     * 获取request请求的信息
     *
     * @param joinPoint
     */
    private void getRequestObj(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        HttpServletRequest request = null;
        for (Object arg : args) {
            if (arg instanceof HttpServletRequest) {
                request = (HttpServletRequest) arg;
                String url = request.getRequestURL().toString();
                String ip = getAddrIp(request);
                log.info(" [ ip 地址: {} ]", ip);
                log.info(" [ 请求地址: {} ]", url);
                break;
            }
        }
    }



    /** 前置通知 */
    @Before(value = "controllerAspect()")
    public void controllerBefore(JoinPoint joinPoint) {
        log.info("进入前置通知");
        startTime.set(System.currentTimeMillis());
        try {
            log.info(" [ 执行方法: {},描述:{},{}() ]", joinPoint.getTarget().getClass().getName(),
                    getControllerMethodDescription(joinPoint),joinPoint.getSignature().getName());
            // 获取请求信息
            getRequestObj(joinPoint);
        } catch (Exception e) {
            log.error(" [ @Before : 发生了异常 ] ");
            log.error(ERROR_INFO, e.getMessage());
        }

    }



    /**
     *   环绕通知：
     *   注意:Spring AOP的环绕通知会影响到AfterThrowing通知的运行,不要同时使用
     *
     *   环绕通知非常强大，可以决定目标方法是否执行，什么时候执行，执行时是否需要替换方法参数，执行完毕是否需要替换返回值。
     *   环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
     */
    @Around(value = "controllerAspect()")
    public Object  around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        // 获取方法参数
        getMethodParam(proceedingJoinPoint);
        return proceedingJoinPoint.proceed();
    }



    @After(value = "controllerAspect()")
    public void controllerAfter(JoinPoint joinPoint) {
        log.info("进入后置通知");
        try {
            log.info(" [ 共耗时长: {} ms ]", System.currentTimeMillis() - startTime.get());
        } finally {
            startTime.remove();
        }
    }

    @AfterReturning(value = "controllerAspect()", returning = "result")
    public void returnController(JoinPoint joinPoint, Object result) {
        try {
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Method method = methodSignature.getMethod();
            boolean outputParam = method.getAnnotation(SystemLog.class).outputParam();
            if (outputParam) {
                log.info(" [ 返回结果: {} ]", JSON.toJSONString(result));
            }
        } catch (Exception e) {
            log.error(" [ @AfterReturning : 发生了异常 ] ");
            log.error(ERROR_INFO, e.getMessage());
        }
    }

    @AfterThrowing(value = "controllerAspect()", throwing = "ex")
    public void throwController(Throwable ex) {
        try {
            log.error(ERROR_INFO, ExceptionUtils.getStackTrace(ex));
        } catch (Exception exception) {
            // step 记录本地异常日志
            log.error(" [ @AfterThrowing: 发生了异常 ] ");
            log.error(ERROR_INFO, ex.getMessage());
        }
    }

    /**
     * 获取注解中对方法的描述信息 用于Controller层注解     
     *
     * @param joinPoint 切点     
     * @return String     
     */
    public String getControllerMethodDescription(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        SystemLog controllerLog = method.getAnnotation(SystemLog.class);
        return controllerLog.description();
    }

}
